#!/usr/bin/env python
#-*- coding:utf-8 -*-

'''
中国大陆身份证号码分析
Refer to:
http://www.nciic.com.cn/framework/gongzuo/index.jsp
http://www.cncard.com/zm/idcard/

To convert this script to executable program in Windows:
1. Install Python 2.6 from http://www.python.org/download/
2. Install cx_freeze fro Python 2.6 from http://cx-freeze.sourceforge.net/
3. Run 'cxfreeze.bat IDParzer.py --target-dir IDParzer' or
       'cxfreeze.bat IDParzer.py --target-dir IDParzer -OO -c'
       to product IDParzer.exe in IDParzer folder
4. Use the IDParzer folder as this program package
'''

import os
import sys
import cPickle
import datetime as dt
import gzip

class IdCard(object):
    def __init__(self, number):
        self.num = number.upper()
        length = len(self.num)
        self._status = 0
        self._addr = ''

        '''if length is 15, convert to 18 format'''
        if (length == 15) and self.num.isdigit():
            self.num = self.num[0:6]+'19'+self.num[6:]
            self.num += self._get_last_number()

        if len(self.num) != 18:
            self._status = 1
        elif not self._check_char():
            self._status = 2
        elif not self._check_last_number():
            self._status = 3
        elif not self._check_birthday():
            self._status = 4
        elif not self._restore_addr_dict():
            self._status = 5
        elif not self._check_address():
            self._status = 6

    def _restore_addr_dict(self):
        ''' stored address dict to gzip file, so restore it '''
        zfname = 'addr.gz'
        if os.path.exists(zfname):
            f = gzip.open(zfname, 'rb')
            try:
                self._addrdict = dict(cPickle.load(f))
            except:
                return False
            finally:
                f.close()
            return True
        else:
            print u'找不到数据文件.'
            return False

    def _get_last_number(self):
        assert(len(self.num) > 16)
        return '10X98765432'[sum([int(x[0])*x[1] for x in zip(self.num[0:17],\
               [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2])]) % 11]

    def _check_char(self):
        if not self.num[0:17].isdigit(): #身份证号码前17位必须是数字
            return False

        if self.num[17].upper() not in '10X98765432':
            return False #身份证号码最后一位数字错误

        return True

    def _check_last_number(self):
        if self._get_last_number() == self.num[17].upper():
            return True
        else:
            return False

    def _check_address(self):
        if (int(self.num[0]) < 1) or (int(self.num[0]) > 8):
            return False

        if self.num[0:6] in self._addrdict:
            self._addr = self._addrdict[self.num[0:6]]
            return True
        elif self.num[0:4]+'00' in self._addrdict:
            self._addr = self._addrdict[self.num[0:4]+'00']
            return True
        elif self.num[0:2]+'0000' in self._addrdict:
            self._addr = self._addrdict[self.num[0:2]+'0000']
            return True
        else:
            return False

    def _check_birthday(self):
        if (int(self.num[6:10]) > dt.date.today().year) or \
           (int(self.num[6:10]) < 1800):
            return False #年份数字错误

        try:
            dt.datetime.strptime(self.num[6:14], '%Y%m%d')
        except ValueError:
            return False #年月日数字错误

        return True

    def get_return_msg(self):
        return { 0: u'成功',    1: u'长度错误',     2: u'号码错误', 3: u'末字符错误',\
                 4: u'日期错误', 5: u'数据文件错误', 6: u'地址未知' }[self._status]

    '''Return (address, sex, birthday) if ID number is valid'''
    def get_card_info(self):
        return (self._addr, int(self.num[16])%2,\
                (self.num[6:10], self.num[10:12], self.num[12:14]))\
                if self._status == 0 else False

def main(argv):
    if len(argv) > 1:
        idcard = IdCard(argv[1])
    else:
        '''raw_input(u'请输入身份证号码:') does not work for unicode prompt'''
        print u'请输入身份证号码:',
        try:
            idcard = IdCard(raw_input().strip())
        except KeyboardInterrupt:
            return

    idinfo = idcard.get_card_info()
    if idinfo:
        print u'身份证号码:\t', idcard.num
        print u'出生地:\t\t', idinfo[0]
        if idinfo[1]:
            print u'性别:\t\t男'
        else:
            print u'性别:\t\t女'
        print u'出生年月日:\t%s年%s月%s日' % idinfo[2]
    else:
        print idcard.get_return_msg()

if __name__ == '__main__':
    main(sys.argv)
